/*-------------------------------------------------------------------------
 *
 * indexam.c
 *	  一般索引访问方法例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/index/indexam.c
 *
 * INTERFACE ROUTINES
 *		index_open		- 通过关系OID打开索引关系
 *		index_close		- 关闭索引关系
 *		index_beginscan - 开始用amgettuple扫描索引
 *		index_beginscan_bitmap - 开始用amgetbitmap扫描索引
 *		index_rescan	- 重新开始扫描索引
 *		index_endscan	- 结束扫描
 *		index_insert	- 将索引元组插入关系
 *		index_markpos	- 标记扫描位置
 *		index_restrpos	- 恢复扫描位置
 *		index_parallelscan_estimate - 估算并行扫描的共享内存
 *		index_parallelscan_initialize - 初始化并行扫描
 *		index_parallelrescan  - （重新）开始索引的并行扫描
 *		index_beginscan_parallel - 加入并行索引扫描
 *		index_getnext_tid	- 从扫描中获取下一个TID
 *		index_fetch_heap		- 获取扫描的下一个堆元组
 *		index_getnext_slot	- 从扫描中获取下一个元组
 *		index_getbitmap - 从扫描中获取所有元组
 *		index_bulk_delete	- 批量删除索引元组
 *		index_vacuum_cleanup	- 删除后的索引清理
 *		index_can_return	- 索引是否支持仅索引扫描？
 *		index_getprocid - 获取支持过程OID
 *		index_getprocinfo - 获取支持过程的查找信息
 *
 * NOTES
 *		本文件包含用于索引_例程，这些例程以前是access/genam中的零散集合。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/amapi.h"
#include "access/heapam.h"
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xlog.h"
#include "catalog/index.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"


/* ----------------------------------------------------------------
 *					用于索引_ 例程的宏
 *
 * 注意：RELATION_CHECKS 中的 ReindexIsProcessingIndex() 检查是为了确保我们
 * 不会尝试扫描或对当前正在重建或待重建的索引进行逐条插入。这有助于捕捉
 * 在重新索引系统目录时不工作的事情，以及防止用户错误，例如索引表达式
 * 访问它们自己的表。该检查不会阻止实际的重建，因为在调用索引 AM 的
 * ambuild 例程时我们不使用 RELATION_CHECKS，并且 ambuild 没有理由
 * 通过这个文件调用其子例程。
 * ----------------------------------------------------------------
 */
#define RELATION_CHECKS \
do { \
	Assert(RelationIsValid(fc_indexRelation)); \
	Assert(PointerIsValid(fc_indexRelation->rd_indam)); \
	if (unlikely(ReindexIsProcessingIndex(RelationGetRelid(fc_indexRelation)))) \
		ereport(ERROR, \
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
				 errmsg("cannot access index \"%s\" while it is being reindexed", \
						RelationGetRelationName(fc_indexRelation)))); \
} while(0)

#define SCAN_CHECKS \
( \
	AssertMacro(IndexScanIsValid(fc_scan)), \
	AssertMacro(RelationIsValid(fc_scan->indexRelation)), \
	AssertMacro(PointerIsValid(fc_scan->indexRelation->rd_indam)) \
)

#define CHECK_REL_PROCEDURE(pname) \
do { \
	if (fc_indexRelation->rd_indam->pname == NULL) \
		elog(ERROR, "function \"%s\" is not defined for index \"%s\"", \
			 CppAsString(pname), RelationGetRelationName(fc_indexRelation)); \
} while(0)

#define CHECK_SCAN_PROCEDURE(pname) \
do { \
	if (fc_scan->indexRelation->rd_indam->pname == NULL) \
		elog(ERROR, "function \"%s\" is not defined for index \"%s\"", \
			 CppAsString(pname), RelationGetRelationName(fc_scan->indexRelation)); \
} while(0)

static IndexScanDesc fc_index_beginscan_internal(Relation fc_indexRelation,
											  int fc_nkeys, int fc_norderbys, Snapshot fc_snapshot,
											  ParallelIndexScanDesc fc_pscan, bool fc_temp_snap);
static inline void fc_validate_relation_kind(Relation fc_r);


/* ----------------------------------------------------------------
 *				   索引_ 接口函数
 * ----------------------------------------------------------------
 */

/* ----------------
 *		index_open - 通过关系 OID 打开索引关系
 *
 *		如果 lockmode 不是 "NoLock"，则会在索引上获得指定类型的锁。
 *		（一般而言，NoLock 应该只在调用者知道它已经对索引
 *		拥有某种适当的锁时使用。）
 *
 *		如果索引不存在，将引发错误。
 *
 *		这是一个方便的例程，适用于索引扫描使用。
 *		一些调用者可能更喜欢直接使用 relation_open。
 * ----------------
 */
Relation index_open(Oid fc_relationId, LOCKMODE fc_lockmode)
{
	Relation	fc_r;

	fc_r = relation_open(fc_relationId, fc_lockmode);

	fc_validate_relation_kind(fc_r);

	return fc_r;
}

/* ----------------
 *		try_index_open - 通过关系 OID 打开索引关系
 *
 *		与 index_open 相同，除了如果关系不存在就返回 NULL 而不是失败。
 * ----------------
 */
Relation try_index_open(Oid fc_relationId, LOCKMODE fc_lockmode)
{
	Relation	fc_r;

	fc_r = try_relation_open(fc_relationId, fc_lockmode);

	/* 如果索引不存在则离开 */
	if (!fc_r)
		return NULL;

	fc_validate_relation_kind(fc_r);

	return fc_r;
}

/* ----------------
 *		index_close - 关闭索引关系
 *
 *		如果 lockmode 不是 "NoLock"，则释放指定的锁。
 *
 *		请注意，通常在 index_close 之后保持锁是合理的；
 *		在这种情况下，锁将在事务结束时自动释放。
 * ----------------
 */
void index_close(Relation fc_relation, LOCKMODE fc_lockmode)
{
	LockRelId	fc_relid = fc_relation->rd_lockInfo.lockRelId;

	Assert(fc_lockmode >= NoLock && fc_lockmode < MAX_LOCKMODES);

	/* 关系缓存执行实际工作... */
	RelationClose(fc_relation);

	if (fc_lockmode != NoLock)
		UnlockRelationId(&fc_relid, fc_lockmode);
}

/* ----------------
 *		validate_relation_kind - 检查关系的种类
 *
 *		确保 relkind 是一个索引或分区索引。
 * ----------------
 */
static inline void fc_validate_relation_kind(Relation fc_r)
{
	if (fc_r->rd_rel->relkind != RELKIND_INDEX &&
		fc_r->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not an index",
						RelationGetRelationName(fc_r))));
}


/* ----------------
 *		index_insert - 将索引元组插入关系中
 * ----------------
 */
bool index_insert(Relation fc_indexRelation,
			 Datum *fc_values,
			 bool *fc_isnull,
			 ItemPointer fc_heap_t_ctid,
			 Relation fc_heapRelation,
			 IndexUniqueCheck fc_checkUnique,
			 bool fc_indexUnchanged,
			 IndexInfo *fc_indexInfo)
{
	RELATION_CHECKS;
	CHECK_REL_PROCEDURE(aminsert);

	if (!(fc_indexRelation->rd_indam->ampredlocks))
		CheckForSerializableConflictIn(fc_indexRelation,
									   (ItemPointer) NULL,
									   InvalidBlockNumber);

	return fc_indexRelation->rd_indam->aminsert(fc_indexRelation, fc_values, fc_isnull,
											 fc_heap_t_ctid, fc_heapRelation,
											 fc_checkUnique, fc_indexUnchanged,
											 fc_indexInfo);
}

/*
 * index_beginscan - 开始使用 amgettuple 扫描索引
 *
 * 调用者必须在堆和索引上持有适当的锁。
 */
IndexScanDesc index_beginscan(Relation fc_heapRelation,
				Relation fc_indexRelation,
				Snapshot fc_snapshot,
				int fc_nkeys, int fc_norderbys)
{
	IndexScanDesc fc_scan;

	fc_scan = fc_index_beginscan_internal(fc_indexRelation, fc_nkeys, fc_norderbys, fc_snapshot, NULL, false);

	/*
	 * 将附加参数保存到 scandesc 中。其他所有参数都是通过
	 * RelationGetIndexScan 设置的。
	 */
	fc_scan->heapRelation = fc_heapRelation;
	fc_scan->xs_snapshot = fc_snapshot;

	/* 准备从表中获取索引匹配项 */
	fc_scan->xs_heapfetch = table_index_fetch_begin(fc_heapRelation);

	return fc_scan;
}

/*
 * index_beginscan_bitmap - 开始使用 amgetbitmap 扫描索引
 *
 * 与上述相同，调用者最好在父堆关系上持有一些锁，
 * 尽管这里没有明确提到。
 */
IndexScanDesc index_beginscan_bitmap(Relation fc_indexRelation,
					   Snapshot fc_snapshot,
					   int fc_nkeys)
{
	IndexScanDesc fc_scan;

	fc_scan = fc_index_beginscan_internal(fc_indexRelation, fc_nkeys, 0, fc_snapshot, NULL, false);

	/*
	 * 将附加参数保存到 scandesc 中。其他所有参数都是通过
	 * RelationGetIndexScan 设置的。
	 */
	fc_scan->xs_snapshot = fc_snapshot;

	return fc_scan;
}

/*
 * index_beginscan_internal --- index_beginscan变体的共同代码
 */
static IndexScanDesc
fc_index_beginscan_internal(Relation fc_indexRelation,
						 int fc_nkeys, int fc_norderbys, Snapshot fc_snapshot,
						 ParallelIndexScanDesc fc_pscan, bool fc_temp_snap)
{
	IndexScanDesc fc_scan;

	RELATION_CHECKS;
	CHECK_REL_PROCEDURE(ambeginscan);

	if (!(fc_indexRelation->rd_indam->ampredlocks))
		PredicateLockRelation(fc_indexRelation, fc_snapshot);

	/*
	 * 在扫描过程中，我们持有relcache条目的引用计数。
	 */
	RelationIncrementReferenceCount(fc_indexRelation);

	/*
	 * 告诉AM打开一个扫描。
	 */
	fc_scan = fc_indexRelation->rd_indam->ambeginscan(fc_indexRelation, fc_nkeys,
												fc_norderbys);
	/* 初始化并行扫描的信息。 */
	fc_scan->parallel_scan = fc_pscan;
	fc_scan->xs_temp_snap = fc_temp_snap;

	return fc_scan;
}

/* ----------------
 *		index_rescan  - (重新)开始索引的扫描
 *
 * 在重新启动过程中，调用者可以指定一组新的scankeys和/或
 * orderbykeys；但是键的数量不能与index_beginscan告诉的不同。
 * （以后我们可能会放宽到“不得超过”，但目前
 * 索引AM倾向于假设scan->numberOfKeys是需要相信的。）
 * 要在不更改键的情况下重新启动扫描，传递NULL作为键数组。
 * （当然，keys *必须*在第一次调用时传递，除非
 * scan->numberOfKeys为零。）
 * ----------------
 */
void index_rescan(IndexScanDesc fc_scan,
			 ScanKey fc_keys, int fc_nkeys,
			 ScanKey fc_orderbys, int fc_norderbys)
{
	SCAN_CHECKS;
	CHECK_SCAN_PROCEDURE(amrescan);

	Assert(fc_nkeys == fc_scan->numberOfKeys);
	Assert(fc_norderbys == fc_scan->numberOfOrderBys);

	/* 释放来自表访问的资源（如缓冲区固定） */
	if (fc_scan->xs_heapfetch)
		table_index_fetch_reset(fc_scan->xs_heapfetch);

	fc_scan->kill_prior_tuple = false; /* 出于安全考虑 */
	fc_scan->xs_heap_continue = false;

	fc_scan->indexRelation->rd_indam->amrescan(fc_scan, fc_keys, fc_nkeys,
											fc_orderbys, fc_norderbys);
}

/* ----------------
 *		index_endscan - 结束扫描
 * ----------------
 */
void index_endscan(IndexScanDesc fc_scan)
{
	SCAN_CHECKS;
	CHECK_SCAN_PROCEDURE(amendscan);

	/* 释放来自表访问的资源（如缓冲区固定） */
	if (fc_scan->xs_heapfetch)
	{
		table_index_fetch_end(fc_scan->xs_heapfetch);
		fc_scan->xs_heapfetch = NULL;
	}

	/* 结束AM的扫描 */
	fc_scan->indexRelation->rd_indam->amendscan(fc_scan);

	/* 释放index_beginscan获得的索引引用计数 */
	RelationDecrementReferenceCount(fc_scan->indexRelation);

	if (fc_scan->xs_temp_snap)
		UnregisterSnapshot(fc_scan->xs_snapshot);

	/* 释放扫描数据结构本身 */
	IndexScanEnd(fc_scan);
}

/* ----------------
 *		index_markpos  - 标记扫描位置
 * ----------------
 */
void index_markpos(IndexScanDesc fc_scan)
{
	SCAN_CHECKS;
	CHECK_SCAN_PROCEDURE(ammarkpos);

	fc_scan->indexRelation->rd_indam->ammarkpos(fc_scan);
}

/* ----------------
 *		index_restrpos	- 恢复扫描位置
 *
 * 注意：这仅恢复索引AM的内部扫描状态。参见
 * ExecRestrPos()的注释。
 *
 * 注意：对于堆，在存在HOT链的情况下，标记/恢复仅在
 * 扫描的快照是MVCC安全时才会正确工作；这确保每个HOT链中最多
 * 只有一个可返回的元组，因此在索引AM的粒度下恢复之前的
 * 状态是足够的。由于目前唯一使用标记/恢复功能的用户是
 * nodeMergejoin.c，这实际上意味着合并连接计划仅适用于
 * MVCC快照。如果必要，可以修复，但目前似乎不太重要。
 * ----------------
 */
void index_restrpos(IndexScanDesc fc_scan)
{
	Assert(IsMVCCSnapshot(fc_scan->xs_snapshot));

	SCAN_CHECKS;
	CHECK_SCAN_PROCEDURE(amrestrpos);

	/* 释放来自表访问的资源（如缓冲区固定） */
	if (fc_scan->xs_heapfetch)
		table_index_fetch_reset(fc_scan->xs_heapfetch);

	fc_scan->kill_prior_tuple = false; /* 出于安全考虑 */
	fc_scan->xs_heap_continue = false;

	fc_scan->indexRelation->rd_indam->amrestrpos(fc_scan);
}

/*
 * index_parallelscan_estimate - 估计并行扫描所需的共享内存
 *
 * 目前，我们没有向AM特定的估算器传递任何信息，
 * 所以它可能只能返回一个常量。将来，我们可能需要
 * 传递更多信息。
 */
Size index_parallelscan_estimate(Relation fc_indexRelation, Snapshot fc_snapshot)
{
	Size		fc_nbytes;

	RELATION_CHECKS;

	fc_nbytes = offsetof(ParallelIndexScanDescData, ps_snapshot_data);
	fc_nbytes = add_size(fc_nbytes, EstimateSnapshotSpace(fc_snapshot));
	fc_nbytes = MAXALIGN(fc_nbytes);

	/*
	 * 如果没有提供amestimateparallelscan，则假设不需要
	 * AM特定数据。 （很难相信这能工作，但
	 * 这里处理起来很简单。）
	 */
	if (fc_indexRelation->rd_indam->amestimateparallelscan != NULL)
		fc_nbytes = add_size(fc_nbytes,
						  fc_indexRelation->rd_indam->amestimateparallelscan());

	return fc_nbytes;
}

/*
 * index_parallelscan_initialize - 初始化并行扫描
 *
 * 我们初始化ParallelIndexScanDesc本身和其后面的AM特定
 * 信息。
 *
 * 此函数调用访问方法特定的初始化例程，以
 * 初始化am特定信息。只在领导进程中调用一次；
 * 然后，单个工作者通过index_beginscan_parallel附加。
 */
void index_parallelscan_initialize(Relation fc_heapRelation, Relation fc_indexRelation,
							  Snapshot fc_snapshot, ParallelIndexScanDesc fc_target)
{
	Size		fc_offset;

	RELATION_CHECKS;

	fc_offset = add_size(offsetof(ParallelIndexScanDescData, ps_snapshot_data),
					  EstimateSnapshotSpace(fc_snapshot));
	fc_offset = MAXALIGN(fc_offset);

	fc_target->ps_relid = RelationGetRelid(fc_heapRelation);
	fc_target->ps_indexid = RelationGetRelid(fc_indexRelation);
	fc_target->ps_offset = fc_offset;
	SerializeSnapshot(fc_snapshot, fc_target->ps_snapshot_data);

	/* aminitparallelscan 是可选的；如果 AM 没有提供，则假设为无操作 */
	if (fc_indexRelation->rd_indam->aminitparallelscan != NULL)
	{
		void	   *fc_amtarget;

		fc_amtarget = OffsetToPointer(fc_target, fc_offset);
		fc_indexRelation->rd_indam->aminitparallelscan(fc_amtarget);
	}
}

/* ----------------
 *		index_parallelrescan  - (重新)启动索引的并行扫描
 * ----------------
 */
void index_parallelrescan(IndexScanDesc fc_scan)
{
	SCAN_CHECKS;

	if (fc_scan->xs_heapfetch)
		table_index_fetch_reset(fc_scan->xs_heapfetch);

	/* amparallelrescan 是可选的；如果 AM 没有提供，则假设为无操作 */
	if (fc_scan->indexRelation->rd_indam->amparallelrescan != NULL)
		fc_scan->indexRelation->rd_indam->amparallelrescan(fc_scan);
}

/*
 * index_beginscan_parallel - 连接并行索引扫描
 *
 * 调用者必须对堆和索引持有适当的锁。
 */
IndexScanDesc index_beginscan_parallel(Relation fc_heaprel, Relation fc_indexrel, int fc_nkeys,
						 int fc_norderbys, ParallelIndexScanDesc fc_pscan)
{
	Snapshot	fc_snapshot;
	IndexScanDesc fc_scan;

	Assert(RelationGetRelid(fc_heaprel) == fc_pscan->ps_relid);
	fc_snapshot = RestoreSnapshot(fc_pscan->ps_snapshot_data);
	RegisterSnapshot(fc_snapshot);
	fc_scan = fc_index_beginscan_internal(fc_indexrel, fc_nkeys, fc_norderbys, fc_snapshot,
									fc_pscan, true);

	/*
	 * 将额外参数保存到 scandesc 中。其他所有内容由 index_beginscan_internal 设置。
	 */
	fc_scan->heapRelation = fc_heaprel;
	fc_scan->xs_snapshot = fc_snapshot;

	/* 准备从表中获取索引匹配项 */
	fc_scan->xs_heapfetch = table_index_fetch_begin(fc_heaprel);

	return fc_scan;
}

/* ----------------
 * index_getnext_tid - 从扫描中获取下一个 TID
 *
 * 结果是下一个满足扫描键的 TID，
 * 如果没有更多匹配的元组，则为 NULL。
 * ----------------
 */
ItemPointer index_getnext_tid(IndexScanDesc fc_scan, ScanDirection fc_direction)
{
	bool		fc_found;

	SCAN_CHECKS;
	CHECK_SCAN_PROCEDURE(amgettuple);

	/* XXX: 我们应该断言已经推送或注册了一个快照 */
	Assert(TransactionIdIsValid(RecentXmin));

	/*
	 * AM 的 amgettuple 过程找到下一个匹配扫描键的索引项，
	 * 并将 TID 放入 scan->xs_heaptid 中。它还应该设置 
	 * scan->xs_recheck 和可能的 scan->xs_itup/scan->xs_hitup，尽管我们
	 * 在这里不关注这些字段。
	 */
	fc_found = fc_scan->indexRelation->rd_indam->amgettuple(fc_scan, fc_direction);

	/* 为安全起见立即重置杀死标志 */
	fc_scan->kill_prior_tuple = false;
	fc_scan->xs_heap_continue = false;

	/* 如果我们没有更多的索引项，就完成了 */
	if (!fc_found)
	{
		/* 释放来自表访问的资源（如缓冲区固定） */
		if (fc_scan->xs_heapfetch)
			table_index_fetch_reset(fc_scan->xs_heapfetch);

		return NULL;
	}
	Assert(ItemPointerIsValid(&fc_scan->xs_heaptid));

	pgstat_count_index_tuples(fc_scan->indexRelation, 1);

	/* 返回我们找到的元组的 TID。 */
	return &fc_scan->xs_heaptid;
}

/* ----------------
 *		index_fetch_heap - 获取扫描的下一个堆元组
 *
 * 结果是与 index_getnext_tid 最近获取的索引 TID 关联的可见堆元组，
 * 如果没有更多匹配的元组，则为 NULL。（由于 HOT 链，可能存在多个匹配的元组，
 * 尽管在使用 MVCC 快照时不应该存在超过一个这样的元组。）
 *
 * 成功时，包含堆元组的缓冲区被锁定（锁定将在未来的 index_getnext_tid、index_fetch_heap 或 index_endscan
 * 调用中释放）。
 *
 * 注意：调用者必须检查 scan->xs_recheck，并在必要时重新检查扫描键。
 * 我们在这里不这样做，因为我们没有足够的信息在一般情况下高效地执行此操作。
 * ----------------
 */
bool index_fetch_heap(IndexScanDesc fc_scan, TupleTableSlot *fc_slot)
{
	bool		fc_all_dead = false;
	bool		fc_found;

	fc_found = table_index_fetch_tuple(fc_scan->xs_heapfetch, &fc_scan->xs_heaptid,
									fc_scan->xs_snapshot, fc_slot,
									&fc_scan->xs_heap_continue, &fc_all_dead);

	if (fc_found)
		pgstat_count_heap_fetch(fc_scan->indexRelation);

	/*
	 * 如果我们扫描了整个 HOT 链并且只找到死元组，请告知索引
	 * AM 杀死该 TID 的条目（这将在下一次 amgettuple 调用中生效，
	 * 在 index_getnext_tid 中）。在恢复时我们不这样做，因为这样可能
	 * 违反 MVCC。请参见 RelationGetIndexScan() 中的注释。
	 */
	if (!fc_scan->xactStartedInRecovery)
		fc_scan->kill_prior_tuple = fc_all_dead;

	return fc_found;
}

/* ----------------
 *		index_getnext_slot - 获取扫描中的下一个元组
 *
 * 如果找到了满足扫描键和快照的元组，则结果为真；否则为假。该元组存储在指定的插槽中。
 *
 * 成功时，资源（如缓冲区针）可能会被占用，并将在未来的 index_getnext_tid、index_fetch_heap 或 index_endscan 调用中释放。
 *
 * 注意：调用者必须检查 scan->xs_recheck，并在必要时对扫描键进行重新检查。我们在这里不这样做，因为我们没有足够的信息在一般情况下有效地执行此操作。
 * ----------------
 */
bool index_getnext_slot(IndexScanDesc fc_scan, ScanDirection fc_direction, TupleTableSlot *fc_slot)
{
	for (;;)
	{
		if (!fc_scan->xs_heap_continue)
		{
			ItemPointer fc_tid;

			/* 从索引中获取下一个 TID 的时间 */
			fc_tid = index_getnext_tid(fc_scan, fc_direction);

			/* 如果我们没有更多的索引项，就完成了 */
			if (fc_tid == NULL)
				break;

			Assert(ItemPointerEquals(fc_tid, &fc_scan->xs_heaptid));
		}

		/*
		 * 获取此索引条目的下一个（或唯一的）可见堆元组。
		 * 如果没有找到任何内容，则循环并从索引中获取下一个 TID。
		 */
		Assert(ItemPointerIsValid(&fc_scan->xs_heaptid));
		if (index_fetch_heap(fc_scan, fc_slot))
			return true;
	}

	return false;
}

/* ----------------
 *		index_getbitmap - 一次性获取所有元组的索引扫描
 *
 * 将所有满足扫描键的堆元组的 TID 添加到位图中。
 * 由于索引扫描与最终堆访问之间没有互锁，因此只有在使用基于 MVCC 的快照时才安全：堆项插槽在我们到达时可能已被较新的元组替换。
 *
 * 返回找到的匹配元组的数量。（注意：这可能只是近似值，因此应仅用于统计目的。）
 * ----------------
 */
int64 index_getbitmap(IndexScanDesc fc_scan, TIDBitmap *fc_bitmap)
{
	int64		fc_ntids;

	SCAN_CHECKS;
	CHECK_SCAN_PROCEDURE(amgetbitmap);

	/* 只需确保这个是假的... */
	fc_scan->kill_prior_tuple = false;

	/*
	 * 让 am 的 getbitmap 过程完成所有工作。
	 */
	fc_ntids = fc_scan->indexRelation->rd_indam->amgetbitmap(fc_scan, fc_bitmap);

	pgstat_count_index_tuples(fc_scan->indexRelation, fc_ntids);

	return fc_ntids;
}

/* ----------------
 *		index_bulk_delete - 执行索引条目的批量删除
 *
 *		回调例程告诉某个给定的主堆元组是否被删除
 *
 *		返回值是一个可选的 palloc'd 统计结构
 * ----------------
 */
IndexBulkDeleteResult * index_bulk_delete(IndexVacuumInfo *fc_info,
				  IndexBulkDeleteResult *fc_istat,
				  IndexBulkDeleteCallback fc_callback,
				  void *fc_callback_state)
{
	Relation	fc_indexRelation = fc_info->index;

	RELATION_CHECKS;
	CHECK_REL_PROCEDURE(ambulkdelete);

	return fc_indexRelation->rd_indam->ambulkdelete(fc_info, fc_istat,
												 fc_callback, fc_callback_state);
}

/* ----------------
 *		index_vacuum_cleanup - 执行索引的删除后清理
 *
 *		返回值是一个可选的 palloc'd 统计结构
 * ----------------
 */
IndexBulkDeleteResult * index_vacuum_cleanup(IndexVacuumInfo *fc_info,
					 IndexBulkDeleteResult *fc_istat)
{
	Relation	fc_indexRelation = fc_info->index;

	RELATION_CHECKS;
	CHECK_REL_PROCEDURE(amvacuumcleanup);

	return fc_indexRelation->rd_indam->amvacuumcleanup(fc_info, fc_istat);
}

/* ----------------
 *		index_can_return
 *
 *		索引访问方法是否支持对给定列的索引仅扫描？
 * ----------------
 */
bool index_can_return(Relation fc_indexRelation, int fc_attno)
{
	RELATION_CHECKS;

	/* amcanreturn 是可选的；如果 AM 未提供，则假定为假 */
	if (fc_indexRelation->rd_indam->amcanreturn == NULL)
		return false;

	return fc_indexRelation->rd_indam->amcanreturn(fc_indexRelation, fc_attno);
}

/* ----------------
 *		index_getprocid
 *
 *		索引访问方法通常需要支持程序，这些程序并不是任何 WHERE 子句查询操作符的直接实现，因此不能保存在 pg_amop 中。相反，这些程序保存在 pg_amproc 中。这些注册的过程 OID 根据访问方法建立的约定分配编号。
 *		一般的索引代码对涉及的例程一无所知；它只为每个定义了索引的属性构建一个这些例程的有序列表。
 *
 *		从 Postgres 8.3 开始，操作符族中的支持例程进一步按它们支持的查询操作符的“左类型”和“右类型”进行细分。对于特定索引属性的“默认”函数是两种类型都等于索引 opclass 的 opcintype（注意，这与索引属性的自身类型 subtly different：它也可以是一个二进制兼容类型）。只有默认函数保存在 relcache 条目中 —— 访问方法可以使用 syscache 查找非默认函数。
 *
 *		此例程返回特定索引属性请求的默认过程 OID。
 * ----------------
 */
RegProcedure index_getprocid(Relation fc_irel,
				AttrNumber fc_attnum,
				uint16 fc_procnum)
{
	RegProcedure *fc_loc;
	int			fc_nproc;
	int			fc_procindex;

	fc_nproc = fc_irel->rd_indam->amsupport;

	Assert(fc_procnum > 0 && fc_procnum <= (uint16) fc_nproc);

	fc_procindex = (fc_nproc * (fc_attnum - 1)) + (fc_procnum - 1);

	fc_loc = fc_irel->rd_support;

	Assert(fc_loc != NULL);

	return fc_loc[fc_procindex];
}

/* ----------------
 *		index_getprocinfo
 *
 *		此例程允许索引 AM 在 relcache 中保留支持过程的 fmgr 查找信息。如上所述，只有特定索引属性的“默认”函数会被缓存。
 *
 * 注意：返回值指向缓存的数据，在任何 relcache 重建期间将丢失！因此，或者立即使用 callinfo，或者在获得对索引 rel 的某种锁定后再保存它。
 * ----------------
 */
FmgrInfo *
index_getprocinfo(Relation fc_irel,
				  AttrNumber fc_attnum,
				  uint16 fc_procnum)
{
	FmgrInfo   *fc_locinfo;
	int			fc_nproc;
	int			fc_optsproc;
	int			fc_procindex;

	fc_nproc = fc_irel->rd_indam->amsupport;
	fc_optsproc = fc_irel->rd_indam->amoptsprocnum;

	Assert(fc_procnum > 0 && fc_procnum <= (uint16) fc_nproc);

	fc_procindex = (fc_nproc * (fc_attnum - 1)) + (fc_procnum - 1);

	fc_locinfo = fc_irel->rd_supportinfo;

	Assert(fc_locinfo != NULL);

	fc_locinfo += fc_procindex;

	/* 如果第一次执行，则初始化查找信息 */
	if (fc_locinfo->fn_oid == InvalidOid)
	{
		RegProcedure *fc_loc = fc_irel->rd_support;
		RegProcedure fc_procId;

		Assert(fc_loc != NULL);

		fc_procId = fc_loc[fc_procindex];

		/*
		 * 如果在 IndexSupportInitialize 中未找到函数，则进行投诉。
		 * 这不应该发生，除非系统表中包含索引 opclass 的虚假条目。 （如果 AM 想要允许支持函数是可选的，它可以使用 index_getprocid。）
		 */
		if (!RegProcedureIsValid(fc_procId))
			elog(ERROR, "missing support function %d for attribute %d of index \"%s\"",
				 fc_procnum, fc_attnum, RelationGetRelationName(fc_irel));

		fmgr_info_cxt(fc_procId, fc_locinfo, fc_irel->rd_indexcxt);

		if (fc_procnum != fc_optsproc)
		{
			/* 用 opclass 选项 Const 初始化 locinfo->fn_expr */
			bytea	  **fc_attoptions = RelationGetIndexAttOptions(fc_irel, false);
			MemoryContext fc_oldcxt = MemoryContextSwitchTo(fc_irel->rd_indexcxt);

			set_fn_opclass_options(fc_locinfo, fc_attoptions[fc_attnum - 1]);

			MemoryContextSwitchTo(fc_oldcxt);
		}
	}

	return fc_locinfo;
}

/* ----------------
 *		index_store_float8_orderby_distances
 *
 *		将 AM 距离函数的结果（可能不准确）转换为 ORDER BY 类型，并将其保存到 xs_orderbyvals/xs_orderbynulls 中，以便可能重新检查。
 * ----------------
 */
void index_store_float8_orderby_distances(IndexScanDesc fc_scan, Oid *fc_orderByTypes,
									 IndexOrderByDistance *fc_distances,
									 bool fc_recheckOrderBy)
{
	int			fc_i;

	Assert(fc_distances || !fc_recheckOrderBy);

	fc_scan->xs_recheckorderby = fc_recheckOrderBy;

	for (fc_i = 0; fc_i < fc_scan->numberOfOrderBys; fc_i++)
	{
		if (fc_orderByTypes[fc_i] == FLOAT8OID)
		{
#ifndef USE_FLOAT8_BYVAL
			/* 必须释放任何旧值以避免内存泄漏 */
			if (!fc_scan->xs_orderbynulls[i])
				pfree(DatumGetPointer(fc_scan->xs_orderbyvals[i]));
#endif
			if (fc_distances && !fc_distances[fc_i].isnull)
			{
				fc_scan->xs_orderbyvals[fc_i] = Float8GetDatum(fc_distances[fc_i].value);
				fc_scan->xs_orderbynulls[fc_i] = false;
			}
			else
			{
				fc_scan->xs_orderbyvals[fc_i] = (Datum) 0;
				fc_scan->xs_orderbynulls[fc_i] = true;
			}
		}
		else if (fc_orderByTypes[fc_i] == FLOAT4OID)
		{
			/* 将距离函数的结果转换为 ORDER BY 类型 */
			if (fc_distances && !fc_distances[fc_i].isnull)
			{
				fc_scan->xs_orderbyvals[fc_i] = Float4GetDatum((float4) fc_distances[fc_i].value);
				fc_scan->xs_orderbynulls[fc_i] = false;
			}
			else
			{
				fc_scan->xs_orderbyvals[fc_i] = (Datum) 0;
				fc_scan->xs_orderbynulls[fc_i] = true;
			}
		}
		else
		{
			/*
			 * 如果排序操作符的返回值是其他任何内容，我们
			 * 不知道如何将距离函数计算出的 float8 限制转换为该值。如果没有损失
			 * 结果，执行器实际上不需要我们在这里返回的排序值，因此只有在 *recheck 标志被设置时才坚持进行转换。
			 */
			if (fc_scan->xs_recheckorderby)
				elog(ERROR, "ORDER BY operator must return float8 or float4 if the distance function is lossy");
			fc_scan->xs_orderbynulls[fc_i] = true;
		}
	}
}

/* ----------------
 *      index_opclass_options
 *
 *      解析索引列的操作类特定选项。
 * ----------------
 */
bytea * index_opclass_options(Relation fc_indrel, AttrNumber fc_attnum, Datum fc_attoptions,
					  bool fc_validate)
{
	int			fc_amoptsprocnum = fc_indrel->rd_indam->amoptsprocnum;
	Oid			fc_procid = InvalidOid;
	FmgrInfo   *fc_procinfo;
	local_relopts fc_relopts;

	/* 如果指定，则获取选项支持程序 */
	if (fc_amoptsprocnum != 0)
		fc_procid = index_getprocid(fc_indrel, fc_attnum, fc_amoptsprocnum);

	if (!OidIsValid(fc_procid))
	{
		Oid			fc_opclass;
		Datum		fc_indclassDatum;
		oidvector  *fc_indclass;
		bool		fc_isnull;

		if (!DatumGetPointer(fc_attoptions))
			return NULL;		/* 好的，没有选项，没有程序 */

		/*
		 * 如果操作类的选项解析程序不存在，但指定了操作类选项，则报告错误。
		 */
		fc_indclassDatum = SysCacheGetAttr(INDEXRELID, fc_indrel->rd_indextuple,
										Anum_pg_index_indclass, &fc_isnull);
		Assert(!fc_isnull);
		fc_indclass = (oidvector *) DatumGetPointer(fc_indclassDatum);
		fc_opclass = fc_indclass->values[fc_attnum - 1];

		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("operator class %s has no options",
						generate_opclass_name(fc_opclass))));
	}

	init_local_reloptions(&fc_relopts, 0);

	fc_procinfo = index_getprocinfo(fc_indrel, fc_attnum, fc_amoptsprocnum);

	(void) FunctionCall1(fc_procinfo, PointerGetDatum(&fc_relopts));

	return build_local_reloptions(&fc_relopts, fc_attoptions, fc_validate);
}
